Android 资源文件关系到了很多方面:
所以了解Android 中资源存储的机制对相关的需求是有帮助的。
主要从三个方面来介绍下,资源相关基础、xml 格式介绍、resoures.arsc结构介绍。
资源相关基础
主要有两点:资源分类以及资源配置信息。可以从官网Providing Resources找到相关信息。然后就是资源文件打包的介绍。
资源分类以及配置信息
资源分类:animator、anim、color、drawable、mipmap、layout、menu、raw、values、xml。
资源配置信息(可能会不断的增加新的配置,可以从官网获取最新的Providing Resources):
配置 | 限定符值 |
---|---|
MCC 和 MNC | 示例: mcc310、mcc310-mnc004、mcc208-mnc00等等 |
语言和区域 | 示例:en、fr、en-rUS、fr-rFR、fr-rCA等等 |
布局方向 | ldrtl、ldltr |
smallestWidth | sw< N >dp 示例:sw320dp、sw600dp、sw720dp等等 |
可用宽度 | w< N >dp 示例:w720dp、w1024dp等等 |
可用高度 | h< N >dp 示例:h720dp、h1024dp等等 |
屏幕尺寸 | small normal、large、xlarge |
屏幕纵横比 | long、notlong |
圆形屏幕 | round、notround |
屏幕方向 | port、land |
UI 模式 | car、desk、television、appliance watch |
夜间模式 | night、notnight |
屏幕像素密度 (dpi) | ldpi、mdpi、hdpi、xhdpi、xxhdpi xxxhdpi、nodpi、tvdpi、anydpi |
触摸屏类型 | notouch、finger |
键盘可用性 | keysexposed、keyshidden、keyssoft |
主要文本输入法 | nokeys、qwerty、12key |
导航键可用性 | navexposed、navhidden |
主要非触摸导航方法 | nonav、dpad、trackball、wheel |
平台版本(API 级别) | 示例:v3、v4、v7等等 |
资源文件打包
Android res 中的xml 文件(不包括assets 文件夹里面的xml)以及AndroidManifest.xml 在打包的时候都会被转化成二进制的xml。
这些二进制格式的XML文件分别有一个字符串资源池,用来保存文件中引用到的每一个字符串,包括XML元素标签、属性名称、属性值,以及其它的一切文本值所使用到的字符串。这样原来在文本格式的XML文件中的每一个放置字符串的地方在二进制格式的XML文件中都被替换成一个索引到字符串资源池的整数值。这样做有两个好处:
A. 文件占用更小。例如,假设在原来的文本格式的XML文件中,有四个地方使用的都是同一个字符串,那么在最终编译出来的二进制格式的XML文件中,字符串资源池只有一份字符串值,而引用它的四个地方只占用一个整数值。
B. 解析速度更快。由于在二进制格式的XML文件中,所有的XML元素标签和属性等值都是使用整数来描述的,因此,在解析的过程中,就不再需要进行字符串解析,这样就可以提高解析速度。(来自老罗博客)
其实有一个问题:为什么Android 要分那么多种类的资源,这样不是非常的复杂吗?
因为市面上Android 手机种类很多(不同的价格、文化、语言等适用于不同的人)。为了让软件更加符合一款手机使用就必须适配不同的方案,因此就有很多不同种类的资源。
Android 二进制xml 格式介绍
(上面AndroidManifest 二进制文件格式图片是网上盗的图)
1.String Chunk:解析出字符串的信息,包含所有的字符串。
比如下面的一个Activity 的申明。
|
|
那么String Chunk 中就会把activity、android、configChanges、label、name 等一系列的String 都保存在String Chunk 中(只需要保存一次,这样就减少了存储空间了)。
2.ResourceId Chunk:保存的是AndroidManifest 中用到的系统属性值对应的资源Id。
比如对应的是:0101021b、0101021c、0101020c,解析出来对应的就是android.R
中的versionCode、versionName、minSdkVersion。
3.Start Namespace Chunk:这个跟xml 格式相关。Android 中xml 采用的是Schema 格式,所以这里主要是解析Prefix 和Uri。
比如下图就是一个StartNamespace。
|
|
解析的时候就会得出Prefix 为android,而Uri 为http://schemas.android.com/apk/res/android。
4.StartTag Chunk:这里开始就是xml 中对应的信息了。
下面是一个xml 中相关的信息(是两个StartTag)。
|
|
会解析两个StartTag Chunk 得到相应的信息(里面所有的字符串信息都是保存在String Chunk 中)。每一个属性值(比如上面的minSdkVersion、targetSdkVersion等)会被解析成[Namespace,Uri,Name,ValueString,Data],包含5个值,每个值对应4字节。
5.EndTag Chunk:与StartTag Chunk 相对应。
上面的StartTag Chunk 的代码会与下面相对应。
|
|
6.End Namespace Chunk:与Start Namespace Chunk 相对应。
上面的Start Namespace Chunk 会与下面的内容相对应。
|
|
AndroidManifest.xml 文件格式可以总结一下:
验证了最上面关于二进制xml 文件的总结(老罗的总结):将所有的字符串提取出来,减少了重复字符串的存储,压缩了xml 大小。解析的时候(从StartTagChunk 开始)不在读取字符串,而是直接读取整数,提高解析速度。
res 中的xml 经过编译以后的格式和AndroidManifest.xml 大体是一致的,主要的不同是StringChunk 的格式有一些区别。所以这里也可以看出一些资源优化的点,减少xml 中string,尽量把所有的string 字符串写在string.xml 中。(这里之前跟朋友争论说在xml 写xxdp 也会增大xml 大小,其实是不会的,因为都是占4字节。)
resoures.arsc 文件结构介绍
resoures.arsc 文件(下面简称arsc)中存储的是public.xml(public.xml 文件可以通过apktool 反编译得到,在/res/values 目录下)中所有的内容。内容比较的多和复杂,比如一张图片ic_launcher.png 在多个dpi 中都要存储,一个string(比如:app_name)在不同的语言(比如:values-en、values-fr)中不一样。这些不同的资源配置信息也需要在arsc 中存储起来。(其实打包以后/res/values 目录下面的所有的信息都是保存在arsc 中了,可以通过解压apk 验证,不存在任何values 文件夹。)后续通过id 加载layout,查找drawable、color、string等都跟arsc 中存储的内容相关。
先给出一张网上的神图。
(上面resoures.arsc 文件结构的图片是网上盗的图,上图有一些错误,最下面的Type Spec 和Config List 应该是交替出现的)
主要包含了3个资源池:
- string 资源池:所有定义的string 信息和资源的路径信息。
- type 资源池:所有定义的资源type。例如:attr、drawable、layout、anim等
- key 资源池:所有的定义在各种资源中的name(上面已经有介绍,在public.xml 中存在的name 都会保存在里面)。
string 资源池分成了两部分,string 部分和style 部分(字符串是可以设置style)。
string 部分包含了xml 中定义的string(比如string.xml 中的<string name="app_name">App4</string>
定义的字符串App)、资源的路径(比如res/drawable-xxhdpi-v11/leak_canary_notification.png
)。
style 部分则是存储了string.xml
对应string 的样式。如下:
|
|
下面给出一些string 资源池里面的string 更加好理解。
|
|
字符串资源池解析以后就是存储的资源信息了。
下面就直接介绍后面的存储格式了(Android逆向之旅—解析编译之后的Resource.arsc文件格式更加的详细)。
1.接下来的格式就是直接解析一种资源(对应的是图中的RES_TABLE_TYPE_SPEC_TYPE 中的type id),假设对应的是mipmap 资源。并且会解析出该资源会有哪些不同类型的资源配置(对应的是图中的RES_TABLE_TYPE_SPEC_TYPE 中的资源项组spec 数)。
2.接下来就是mipmap 资源的配置信息(上面第1步假设的mipmap,对应的是图中的RES_TABLE_TYPE_TYPE,会解析出第1步中资源项组spec 数种不同配置信息),解析出配置信息(运营商、locale、屏幕尺寸等等)。然后就会解析出这种配置下所有mipmap 的信息。
3.解析出mipmap 的信息(对应的是图中的ResTable_entry),包括资源对应的名称(比如:ic_launcher),对应的值信息(比如:res/mipmap-mdpi-v4/ic_launcher.png)等等。
最后就是重复的解析第2步给出的第3步数量的ResTable_entry),然后解析第1步给出的第2步数量RES_TABLE_TYPE_TYPE。
所以arsc 的格式可以简单的重新画一个,如下:
所以对于资源的查找应该也有一个比较清晰的认识。通过资源id,取前两位先拿到对应的packageId,取3、4位拿到typeId,这样就定位到package 和type。最后就根据当前设备的维度信息,寻找到最合适的资源。最后如果得到的是资源文件(layout、drawable等)再去解析就可以了。
相关需求
最开始已经有提到过,还可以加一些。包优化AndResGuard。皮肤包ThemeSwitch。
总结
这篇博客在很早之前就写了大部分,主要是在研究项目中关于皮肤代码时的总结(好记性不如烂笔头,所以这次记下来了),并且不是自己完整的研究了arsc 的生成或者xml 的解析,主要是在老罗的博客中边看边学,在尼古拉斯_赵四的博客中慢慢验证的。所以可能是会有错误的,如果你发现了错误,方便的话可以发一份邮件给我(相信你找得到)。
然后Android 中资源相关的内容,个人觉得是非常重要的,熟悉一下还是有很大帮助的。
Providing Resources
Android资源管理框架(Asset Manager)简要介绍和学习计划
Android逆向之旅—解析编译之后的AndroidManifest文件格式
Android逆向之旅—解析编译之后的Resource.arsc文件格式